-
Notifications
You must be signed in to change notification settings - Fork 7.6k
fixing beginTransaction() thread safety #6425
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
My issue here is that the C interface (hal) is no longer safe. Let's find a way for this to work properly on all interfaces :) |
libraries/SPI/src/SPI.cpp
Outdated
@@ -153,6 +154,7 @@ void SPIClass::endTransaction() | |||
if(_inTransaction){ | |||
_inTransaction = false; | |||
spiEndTransaction(_spi); | |||
spiUnlock(_spi); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible bug. spiEndTransaction() is the same as calling spiUnlock(), so you are calling unlock twice.
cores/esp32/esp32-hal-spi.c
Outdated
@@ -1059,7 +1059,7 @@ void spiSimpleTransaction(spi_t * spi) | |||
if(!spi) { | |||
return; | |||
} | |||
SPI_MUTEX_LOCK(); | |||
SPI_MUTEX_UNLOCK(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible bug _LOCK() changed to _UNLOCK(). I will do a more in depth study of the code this weekend.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it should be LOCK as that is another way to start transaction
I'll do more thinking and looking back at the code. A good test sketch is also in order :) |
@@ -106,19 +120,23 @@ void SPIClass::setHwCs(bool use) | |||
|
|||
void SPIClass::setFrequency(uint32_t freq) | |||
{ | |||
//check if last freq changed | |||
SPI_PARAM_LOCK(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protects "_div" from cross-thread modification. OK
} | ||
|
||
void SPIClass::setClockDivider(uint32_t clockDiv) | ||
{ | ||
_div = clockDiv; | ||
SPI_PARAM_LOCK(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protects "_div" from cross-thread modification. OK
@@ -138,7 +156,8 @@ void SPIClass::setBitOrder(uint8_t bitOrder) | |||
|
|||
void SPIClass::beginTransaction(SPISettings settings) | |||
{ | |||
//check if last freq changed | |||
SPI_PARAM_LOCK(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protects "_div" from cross-thread modification. OK
@smarq8 Apologies for the delay. After studying this code for a couple hours, I agree there is an issue in SPIClass::beginTransaction(). esp32-hal-spi.c does include a Mutex for controlling SPI write access and at least some config changes. It appears to be ok. It's SPI.c/h that may not be thread safe due to overwriting local variables in SPI.c. Mutex lifecycle: xSemaphoreCreateMutex and vSemaphoreDelete should be in the class constructor/destructors. See https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/HardwareSerial.cpp. Also you introduced a bug into void spiSimpleTransaction(). Needs to be SPI_MUTEX_LOCK(). Warnings: Possible leak in esp32-hal-spi.c:
|
about _inTransaction I deduce its intention was to be placed after SPI_MUTEX_LOCK() and release after SPI_MUTEX_UNLOCK() so it might be right. However I'm little sceptical here because Im not sure what happen when another task call SPI_MUTEX_LOCK() in between spiTransaction() and SPI_MUTEX_LOCK() especially in dual core env. On the other hand _inTransaction is guarded by spi->lock mutex then again, it should be safe. Im just thinking aloud. @mrengineer7777 you also mentioned about missing vSemaphoreDelete(paramLock), then what about spi->lock destructor? Is it also missing? As well Im woder is it proper to calling vSemaphoreDelete(mutex) in situation where mutex==NULL? |
I'm going to scope this to SPIClass::beginTransaction and endTransaction(). To be honest I'm not 100% on the positioning of _inTransaction. I'll have to defer to the experts. @me-no-dev ?
Do you mean this comment? Yes that's likely a bug that should be fixed.
No harm in adding SPIClass::~SPIClass(), but adding a call to end() in the destructor does change the library behavior. It may be worth leaving that out and starting a separate PR for that. Good catch.
That would be bad. Always check that mutex is valid before deleting. I recommend following the pattern in https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/HardwareSerial.cpp. That implementation appears correct. Also I recommend moving vSemaphoreDelete() to the destructor. For extra safety you can set paramLock=NULL after deleting it. |
Why end() at ~SPIClass() matter? Or you reffer to calling vSemaphoreDelete() inside end() where after you mention that I catch its not proper place because after first end() then it will destroy permanently paramLock unintentionally. Im also not quite sure about at SPI.h.
|
end() in ~SPIClass() matters because it changes the SPI shutdown. Is it the correct way to do it? VERY LIKELY. But it's outside the scope of your PR and might introduce unexpected side effects.
FreeRTOS is included in lots of places, so should be safe to include here all the time (no #if required). I would do the same as in https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/HardwareSerial.h |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible weird bug: In another file the Github compiler doesn't like it if the c++ variables declaration are in a different order than the constructor. I would move paramLock below _inTransaction in SPI.h.
Edit: This is the PR with the variable declaration order issue: #6492
whats wrong with Arduino 11 on ubuntu-latest ?
|
Looks like you fixed it with the missing parenthesis. Looking good. |
Until now I write everything on spec due to being unable to compile on my PC. After I line up my compiler then its now much easier. |
It's probably a bit much, but I'm not a maintainer. They'll likely ask you to flatten it down to one commit before merging. |
I eventually made some working example. It comapre transactions clock divider to expected and print message when not matched.
output in FAIL env
|
Status update: added to 2.0.4 milestone, we will take a look on this in next 2 weeks |
Thanks all for the contribution :) Merging |
according to #6404 here is mentioned PR.
TLTR: isnide beginTransaction() code above spiTransaction() is not protected by mutex and in some situation it might be unexpectedly overwrited by other threads. More descrition in link above.